package com.mlethe.wifitest;

import android.os.Handler;
import android.os.Message;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Socket连接管理类
 *
 * @author Mlethe
 */
public class SocketManager implements Runnable {

    /**
     * 接收数据成功
     */
    public static final int SOCKET_RECEIVE = 0x0099;
    /**
     * socket连接成功
     */
    public static final int SOCKET_CONNECT_SUCCESS = 0x0098;
    /**
     * socket连接失败
     */
    public static final int SOCKET_CONNECT_FAIL = 0x0097;
    /**
     * 服务器断开连接 或 网络错误断开连接
     */
    public static final int SOCKET_UNEXPECTED_CLOSE = 0x0096;
    /**
     * 断开连接成功
     */
    public static final int SOCKET_CLOSE_OK = 0x0095;
    /**
     * 关闭异常
     */
    public static final int SOCKET_CLOSE_ERR = 0x0094;

    private static final String CHARSET_NAME = "UTF-8";

    private boolean noTask, unexpectedClose, isConnected;
    private Socket mSocket;
    private Handler mHandler;
    private BufferedWriter bw = null;
    private int what = 0;
    private BlockingQueue<String> msgPool = new LinkedBlockingQueue<>();;

    private String mIp;
    private int mPort;

    /**
     * 创建对象
     *
     * @param ip      连接地址
     * @param port    连接端口
     * @param handler
     */
    private SocketManager(String ip, int port, Handler handler) {
        this.mIp = ip;
        this.mPort = port;
        this.mHandler = handler;
    }

    /**
     * 创建对象
     *
     * @param socket
     * @param handler
     */
    private SocketManager(Socket socket, Handler handler) {
        this.mSocket = socket;
        this.mHandler = handler;
    }

    /**
     * 创建对象
     *
     * @param ip      连接地址
     * @param port    连接端口
     * @param handler
     * @return
     */
    public static SocketManager create(String ip, int port, Handler handler) {
        return new SocketManager(ip, port, handler);
    }

    /**
     * 创建对象
     *
     * @param socket
     * @param handler
     * @return
     */
    public static SocketManager create(Socket socket, Handler handler) {
        return new SocketManager(socket, handler);
    }

    /**
     * 获取连接地址
     *
     * @return
     */
    public InetAddress getInetAddress() {
        if (mSocket == null) {
            return null;
        }
        return mSocket.getInetAddress();
    }

    /**
     * 发送消息
     *
     * @param s
     */
    public void send(String s) {
        if (mSocket == null) {
            return;
        }
        if (!isConnected || mSocket.isClosed()) {
            return;
        }
        msgPool.add(s);
        what = 1;
        noTask = false;
        synchronized (this) {
            notify();
        }
    }

    /**
     * 关闭连接
     */
    public void close() {
        if (mSocket == null) {
            return;
        }
        if (mSocket.isClosed()) {
            mHandler = null;
            if (msgPool != null) {
                msgPool.clear();
            }
            return;
        }
        what = -1;
        noTask = false;
        synchronized (this) {
            notify();
        }
    }

    /**
     * 是否断开连接
     *
     * @return
     */
    public boolean isClosed() {
        if (mSocket == null) {
            return true;
        }
        return !isConnected || mSocket.isClosed();
    }

    @Override
    public void run() {
        try {
            if (mSocket == null) {
                mSocket = new Socket(mIp, mPort);
            }
            // 返回给客户端数据
            bw = new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), CHARSET_NAME));
            noTask = true;
            isConnected = true;
            sendMessage(SOCKET_CONNECT_SUCCESS, mSocket);
        } catch (IOException e) {
            e.printStackTrace();
            sendEmptyMessage(SOCKET_CONNECT_FAIL);
            return;
        }
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(mSocket.getInputStream(), CHARSET_NAME));
            new Listener(br).start();
        } catch (IOException e) {
            e.printStackTrace();
        }

        while (mSocket != null && !mSocket.isClosed()) {
            synchronized (this) {
                while (noTask) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            noTask = true;
            if (what == 1) {
                try {
                    String msg = msgPool.poll(30, TimeUnit.MINUTES);
                    bw.write(msg + "\n");
                    bw.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                    unexpectedClose();
                }
            } else if (what == -1) {
                try {
                    if (bw != null) {
                        bw.close();
                        bw = null;
                    }
                    if (mSocket != null) {
                        mSocket.close();
                        isConnected = false;
                        mSocket = null;
                    }
                    if (!unexpectedClose) {
                        sendEmptyMessage(SOCKET_CLOSE_OK);
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                    if (!unexpectedClose) {
                        sendEmptyMessage(SOCKET_CLOSE_ERR);
                    }
                } finally {
                    mHandler = null;
                    if (msgPool != null) {
                        msgPool.clear();
                    }
                }
            }
        }
    }

    /**
     * 意外断开
     */
    private void unexpectedClose() {
        if (mSocket.isClosed()) {
            unexpectedClose = true;
            try {
                if (bw != null) {
                    bw.close();
                    bw = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (msgPool != null) {
                msgPool.clear();
            }
            sendEmptyMessage(SOCKET_UNEXPECTED_CLOSE);
            mHandler = null;
        }
    }

    /**
     * 发送消息
     *
     * @param obj
     * @param what
     */
    private void sendMessage(int what, Object obj) {
        if (mHandler != null) {
            Message msg = new Message();
            msg.what = what;
            msg.obj = obj;
            mHandler.sendMessage(msg);
        }
    }

    /**
     * 发送空消息
     *
     * @param what
     */
    private void sendEmptyMessage(int what) {
        if (mHandler != null) {
            mHandler.sendEmptyMessage(what);
        }
    }

    private class Listener extends Thread {

        private BufferedReader br;

        public Listener(BufferedReader br) {
            this.br = br;
        }

        @Override
        public void run() {
            try {
                if (br != null) {
                    String str;
                    while ((str = br.readLine()) != null) {
                        sendMessage(SOCKET_RECEIVE, str);
                    }
                    br.close();
                    br = null;
                }
                // 由远程关闭引发
                unexpectedClose();
            } catch (IOException e) {
                e.printStackTrace();
                // 由socket主动关闭或网络错误引发
                if (isConnected) {
                    unexpectedClose();
                }
            }
        }
    }

    /**
     * 字符串转换为ascii
     *
     * @param content
     * @return
     */
    public static String stringToA(String content) {
        String result = "";
        int max = content.length();
        for (int i = 0; i < max; i++) {
            char c = content.charAt(i);
            int b = (int) c;
            result = result + b;
        }
        return result;
    }
}
